package de.recipeminer.environments;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import de.recipeminer.persistence.Db4oContainer;
import org.apache.log4j.Logger;
import org.yaml.snakeyaml.Yaml;

import java.io.InputStream;
import java.util.Map;
import java.util.Set;

/**
 * This class is used as central information source to query the {@link de.recipeminer.environments.ExecutionContext}
 * the program is currently run in.
 * Note: This is currently a rather fragile implementation, since neither client code is informed about change of
 * the {@code ExecutionContext}, nor are active references of inactive {@ExecutionContexts} invalidated. It is
 * hence currently the repsonsibility of the programmer to ensure the recreation of 
 * {@link de.recipeminer.environments.FileFinder}s and {@link de.recipeminer.persistence.Db4oContainer} instances
 * that are suppossed to point to the current {@code ExecutionContext} after each use of 
 * {@link ExecutionContextProvider#selectContext}
 * 
 * @autor: Markus Ackermann
 */
public class ExecutionContextProvider {
    private static Logger logger = Logger.getLogger(ExecutionContextProvider.class);    
    private static ExecutionContextProvider instance = null;
    private static ExecutionContext last = null;

    private ExecutionContext defaultContext = null;
    private ExecutionContext currentContext = null;
    
    private ImmutableMap<String, Map> foundValidContexts;
    private Map<String, ExecutionContext> loadedContexts = Maps.newHashMap();
   
    private ExecutionContextProvider(String envFileDesc) {
        InputStream envFileStream = new FileFinder("config", "").getStream(envFileDesc);
        Map yamlRoot;
        try {
            yamlRoot = (Map) new Yaml().load(envFileStream);
        } catch (ClassCastException cce) {
            throw new IllegalStateException("Environment configuration file apparently no nested YAML: " + envFileDesc);
        } catch (FileFinder.UnableToLocateException ule) {
            throw new IllegalStateException("Unable to find environment configuration file: ", ule);
        }
        String defaultEnvKey = (String) yamlRoot.get("default_environment");
        Map<String, Map> envMap = new YamlConfigValidator.EnvironmentFileValidator().checkAndGetEnvironments(yamlRoot);
        ImmutableMap.Builder<String, Map> builder = ImmutableMap.builder();
        foundValidContexts = builder.putAll(envMap).build();
        defaultContext = getContext(defaultEnvKey);
        currentContext = getContext(defaultEnvKey);
    }

    public static void init(String envFilePath) {
        instance = new ExecutionContextProvider(envFilePath);
    }

    public static ExecutionContextProvider getInstance() {
        if(instance == null) {
            init("environments.yaml");
        }
        return instance;
    }

    /**
     * Return the default Execution context for this programm execution.
     * It is either specified by the corresponding command line option or
     * (if absent) by the default environment set in the enviroments config file.
     *
     * @return
     */
    public ExecutionContext getDefaultContext() {
        return defaultContext;
    }

    /**
     * Returns the currently actice {@link ExecutionContext}
     *
     * @return the active {@link ExecutionContext}
     */
    public ExecutionContext getCurrentContext() {
        return currentContext;
    }

    /**
     * Sets a new {@link ExecutionContext} as the currently active context for the program
     * runtime.
     *
     * @param name name of the {@link ExecutionContext} to be activated as current context
     * @return the new selected context
     */
    public ExecutionContext selectContext(String name) {
        currentContext = getContext(name);
        return currentContext;
    }

    public ExecutionContext getContext(String name) {
        if( ! getKnownContextNames().contains(name)) {
            throw new IllegalArgumentException("Requested execution context unknown: " + name);
        } else {
            if ( ! loadedContexts.keySet().contains(name)) {
                loadContext(name);
            }
            return loadedContexts.get(name);
        }
    }

    public ExecutionContext selectDefaultContext() {
        currentContext = defaultContext;
        return currentContext;
    }

    public Set<String> getKnownContextNames() {
        return foundValidContexts.keySet();
    }

    public static FileFinder currentResourceFinder() {
        return getInstance().getCurrentContext().getResourceFinder();
    }

    public static FileFinder currentConfigFinder() {
        return getInstance().getCurrentContext().getConfigFinder();
    }

    public static Db4oContainer currentContainer() {
        return getInstance().getCurrentContext().getContainer();
    }
    
    
    private void loadContext(String name) {
        if (loadedContexts.containsKey(name))
            return;
        if (! foundValidContexts.containsKey(name)) {
            throw new IllegalArgumentException("The requested context was not found or not parsed as valid when " +
                    "loading the context config file.");
        } else {
            Map desc = foundValidContexts.get(name);
            loadedContexts.put(name, ExecutionContext.fromDescription(desc));
        }
    }
}
